// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package os

import (
	"errors"
	"internal/poll"
	"internal/syscall/windows"
	"runtime"
	"syscall"
	"unicode/utf16"
	"unsafe"
)

// 文件是*文件的真实表示。
// 额外的间接级别确保操作系统
// 的客户端无法覆盖此数据，这可能导致终结器
// 关闭错误的文件描述符。
type file struct {
	pfd        poll.FD
	name       string
	dirinfo    *dirInfo // nil除非正在读取的目录
	appendMode bool     // 是否打开文件以附加
}

// Fd返回引用打开文件的Windows句柄。
// 如果关闭f，则文件描述符将无效。
// 如果f被垃圾回收，终结器可能会关闭文件描述符，
// 使其无效；有关何时可以运行终结器的更多信息，请参阅runtime.SetFinalizer。在Unix系统上，这将导致setDudate 
// 方法停止工作。
func (file *File) Fd() uintptr {
	if file == nil {
		return uintptr(syscall.InvalidHandle)
	}
	return uintptr(file.pfd.Sysfd)
}

// newFile返回具有给定文件句柄和名称的新文件。
// 与NewFile不同，它不检查h是否为syscall.InvalidHandle。
func newFile(h syscall.Handle, name string, kind string) *File {
	if kind == "file" {
		var m uint32
		if syscall.GetConsoleMode(h, &m) == nil {
			kind = "console"
		}
		if t, err := syscall.GetFileType(h); err == nil && t == syscall.FILE_TYPE_PIPE {
			kind = "pipe"
		}
	}

	f := &File{&file{
		pfd: poll.FD{
			Sysfd:         h,
			IsStream:      true,
			ZeroReadIsEOF: true,
		},
		name: name,
	}}
	runtime.SetFinalizer(f.file, (*file).close)

	// 忽略初始化错误。
	// 假设在以后的I/O中会出现任何问题。
	f.pfd.Init(kind, false)

	return f
}

// 新建控制台文件将创建用作控制台的新文件。
func newConsoleFile(h syscall.Handle, name string) *File {
	return newFile(h, name, "console")
}

// NewFile返回一个具有给定文件描述符和
// 名称的新文件。如果fd不是有效的文件
// 描述符，则返回值将为零。
func NewFile(fd uintptr, name string) *File {
	h := syscall.Handle(fd)
	if h == syscall.InvalidHandle {
		return nil
	}
	return newFile(h, name, "file")
}

// 如果文件描述目录，则提供辅助信息
type dirInfo struct {
	data     syscall.Win32finddata
	needdata bool
	path     string
	isempty  bool // 如果FindFirstFile返回错误，则设置为\u File\u NOT\u FOUND 
}

func epipecheck(file *File, e error) {
}

// DevNull是操作系统的“空设备”名称。“
// 在类似Unix的系统上，则为“/dev/null”；在Windows上，“NUL”。
const DevNull = "NUL"

func (f *file) isdir() bool { return f != nil && f.dirinfo != nil }

func openFile(name string, flag int, perm FileMode) (file *File, err error) {
	r, e := syscall.Open(fixLongPath(name), flag|syscall.O_CLOEXEC, syscallMode(perm))
	if e != nil {
		return nil, e
	}
	return newFile(r, name, "file"), nil
}

func openDir(name string) (file *File, err error) {
	var mask string

	path := fixLongPath(name)

	if len(path) == 2 && path[1] == ':' { // 它是一个驱动器号，如C:
		mask = path + `*`
	} else if len(path) > 0 {
		lc := path[len(path)-1]
		if lc == '/' || lc == '\\' {
			mask = path + `*`
		} else {
			mask = path + `\*`
		}
	} else {
		mask = `\*`
	}
	maskp, e := syscall.UTF16PtrFromString(mask)
	if e != nil {
		return nil, e
	}
	d := new(dirInfo)
	r, e := syscall.FindFirstFile(maskp, &d.data)
	if e != nil {
		// 当
		// 找不到匹配的文件时，FindFirstFile返回错误\u FILE\u NOT \u FOUND。然后，如果目录
		// 存在，我们应该继续。
		if e != syscall.ERROR_FILE_NOT_FOUND {
			return nil, e
		}
		var fa syscall.Win32FileAttributeData
		pathp, e := syscall.UTF16PtrFromString(path)
		if e != nil {
			return nil, e
		}
		e = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
		if e != nil {
			return nil, e
		}
		if fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 {
			return nil, e
		}
		d.isempty = true
	}
	d.path = path
	if !isAbs(d.path) {
		d.path, e = syscall.FullPath(d.path)
		if e != nil {
			return nil, e
		}
	}
	f := newFile(r, name, "dir")
	f.dirinfo = d
	return f, nil
}

// openFileNolog是OpenFile的Windows实现。
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
	if name == "" {
		return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT}
	}
	r, errf := openFile(name, flag, perm)
	if errf == nil {
		return r, nil
	}
	r, errd := openDir(name)
	if errd == nil {
		if flag&O_WRONLY != 0 || flag&O_RDWR != 0 {
			r.Close()
			return nil, &PathError{Op: "open", Path: name, Err: syscall.EISDIR}
		}
		return r, nil
	}
	return nil, &PathError{Op: "open", Path: name, Err: errf}
}

func (file *file) close() error {
	if file == nil {
		return syscall.EINVAL
	}
	if file.isdir() && file.dirinfo.isempty {
		// “特殊”空目录
		return nil
	}
	var err error
	if e := file.pfd.Close(); e != nil {
		if e == poll.ErrFileClosing {
			e = ErrClosed
		}
		err = &PathError{Op: "close", Path: file.name, Err: e}
	}

	// 不再需要终结器
	runtime.SetFinalizer(file, nil)
	return err
}

// seek将下一次读取或写入文件的偏移量设置为偏移量，解释
// 根据何处：0表示相对于文件原点，1表示相对于当前偏移量，2是指相对于终点的距离。
// 返回新的偏移量和错误（如果有）。
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
	ret, err = f.pfd.Seek(offset, whence)
	runtime.KeepAlive(f)
	return ret, err
}

// Truncate更改命名文件的大小。
// 如果文件是符号链接，则会更改链接目标的大小。
func Truncate(name string, size int64) error {
	f, e := OpenFile(name, O_WRONLY|O_CREATE, 0666)
	if e != nil {
		return e
	}
	defer f.Close()
	e1 := f.Truncate(size)
	if e1 != nil {
		return e1
	}
	return nil
}

// Remove删除指定的文件或目录。
// 如果有错误，则类型为*PathError。
func Remove(name string) error {
	p, e := syscall.UTF16PtrFromString(fixLongPath(name))
	if e != nil {
		return &PathError{Op: "remove", Path: name, Err: e}
	}

	// Go文件接口迫使我们知道
	// 名称是文件还是目录。两种都试试。
	e = syscall.DeleteFile(p)
	if e == nil {
		return nil
	}
	e1 := syscall.RemoveDirectory(p)
	if e1 == nil {
		return nil
	}

	// 两个都失败：找出要返回的错误。
	if e1 != e {
		a, e2 := syscall.GetFileAttributes(p)
		if e2 != nil {
			e = e2
		} else {
			if a&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
				e = e1
			} else if a&syscall.FILE_ATTRIBUTE_READONLY != 0 {
				if e1 = syscall.SetFileAttributes(p, a&^syscall.FILE_ATTRIBUTE_READONLY); e1 == nil {
					if e = syscall.DeleteFile(p); e == nil {
						return nil
					}
				}
			}
		}
	}
	return &PathError{Op: "remove", Path: name, Err: e}
}

func rename(oldname, newname string) error {
	e := windows.Rename(fixLongPath(oldname), fixLongPath(newname))
	if e != nil {
		return &LinkError{"rename", oldname, newname, e}
	}
	return nil
}

// 管道返回一对连接的文件；从r读取并返回写入w的字节。
// 返回文件和错误（如果有）。Windows处理基础
// 返回的文件被标记为可由子进程继承。
func Pipe() (r *File, w *File, err error) {
	var p [2]syscall.Handle
	e := syscall.Pipe(p[:])
	if e != nil {
		return nil, nil, NewSyscallError("pipe", e)
	}
	return newFile(p[0], "|0", "pipe"), newFile(p[1], "|1", "pipe"), nil
}

func tempDir() string {
	n := uint32(syscall.MAX_PATH)
	for {
		b := make([]uint16, n)
		n, _ = syscall.GetTempPath(uint32(len(b)), &b[0])
		if n > uint32(len(b)) {
			continue
		}
		if n == 3 && b[1] == ':' && b[2] == '\\' {
			// 对路径不做任何操作，如C:\。
		} else if n > 0 && b[n-1] == '\\' {
			// 否则删除终止\。
			n--
		}
		return string(utf16.Decode(b[:n]))
	}
}

// Link创建newname作为指向oldname文件的硬链接。
// 如果出现错误，则类型为*LinkError。
func Link(oldname, newname string) error {
	n, err := syscall.UTF16PtrFromString(fixLongPath(newname))
	if err != nil {
		return &LinkError{"link", oldname, newname, err}
	}
	o, err := syscall.UTF16PtrFromString(fixLongPath(oldname))
	if err != nil {
		return &LinkError{"link", oldname, newname, err}
	}
	err = syscall.CreateHardLink(n, o, 0)
	if err != nil {
		return &LinkError{"link", oldname, newname, err}
	}
	return nil
}

// Symlink创建newname作为指向oldname的符号链接。
// 在Windows上，指向不存在的旧名称的符号链接将创建一个文件符号链接；
// 如果以后将oldname创建为目录，则符号链接将不起作用。
// 如果出现错误，则类型为*LinkError。
func Symlink(oldname, newname string) error {
	// /'/'在链接的内容中不起作用
	oldname = fromSlash(oldname)

	// 需要旧名称的确切位置（当其相对时），以确定它是否是目录
	destpath := oldname
	if v := volumeName(oldname); v == "" {
		if len(oldname) > 0 && IsPathSeparator(oldname[0]) {
			// 旧名称相对于包含新名称的卷。
			if v = volumeName(newname); v != "" {
				// 显式前置该卷，因为它可能不同于当前工作目录的
				// 卷。
				destpath = v + oldname
			}
		} else {
			// oldname是相对于newname的。
			destpath = dirname(newname) + `\` + oldname
		}
	}

	fi, err := Stat(destpath)
	isdir := err == nil && fi.IsDir()

	n, err := syscall.UTF16PtrFromString(fixLongPath(newname))
	if err != nil {
		return &LinkError{"symlink", oldname, newname, err}
	}
	o, err := syscall.UTF16PtrFromString(fixLongPath(oldname))
	if err != nil {
		return &LinkError{"symlink", oldname, newname, err}
	}

	var flags uint32 = windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
	if isdir {
		flags |= syscall.SYMBOLIC_LINK_FLAG_DIRECTORY
	}
	err = syscall.CreateSymbolicLink(n, o, flags)
	if err != nil {
		// 在Windows 10（1703，v10.0.14972）下，不支持unprivileged create标志
		// 。不使用它重试。
		flags &^= windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
		err = syscall.CreateSymbolicLink(n, o, flags)
		if err != nil {
			return &LinkError{"symlink", oldname, newname, err}
		}
	}
	return nil
}

// openSymlink使用FILE\u FLAG\u OPEN\u repasse\u POINT 
// 参数调用CreateFile Windows API，以便Windows在路径为symlink时不遵循symlink。
// openSymlink返回打开的文件句柄。
func openSymlink(path string) (syscall.Handle, error) {
	p, err := syscall.UTF16PtrFromString(path)
	if err != nil {
		return 0, err
	}
	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
	// 使用文件标志打开重新分析点，否则CreateFile将跟随符号链接。
	// 参见https:
	attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
	h, err := syscall.CreateFile(p, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
	if err != nil {
		return 0, err
	}
	return h, nil
}

// normaliseLinkPath将
// DeviceIoControl（h，FSCTL\u GET\u repasse\u POINT，…）
// 返回的绝对路径转换为所有Windows API均可接受的路径。例如，它将
// /\\C:\foo\bar转换为C:\foo\bar 
// /\\UNC\foo\bar转换为\\foo\bar 
// /\\卷{abc}\转换为C:\
func normaliseLinkPath(path string) (string, error) {
	if len(path) < 4 || path[:4] != `\??\` {
		// 意外路径，按原样返回
		return path, nil
	}
	// 我们的路径以\ \ 
	s := path[4:]
	switch {
	case len(s) >= 2 && s[1] == ':': // /\ \C:\foo\bar 
		return s, nil
	case len(s) >= 4 && s[:4] == `UNC\`: // /\ \UNC\foo\bar 
		return `\\` + s[4:], nil
	}

	// 处理路径，如\ \卷{abc}。。。

	err := windows.LoadGetFinalPathNameByHandle()
	if err != nil {
		// 我们必须使用旧版本的Windows 
		return "", err
	}

	h, err := openSymlink(path)
	if err != nil {
		return "", err
	}
	defer syscall.CloseHandle(h)

	buf := make([]uint16, 100)
	for {
		n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), windows.VOLUME_NAME_DOS)
		if err != nil {
			return "", err
		}
		if n < uint32(len(buf)) {
			break
		}
		buf = make([]uint16, n)
	}
	s = syscall.UTF16ToString(buf)
	if len(s) > 4 && s[:4] == `\\?\` {
		s = s[4:]
		if len(s) > 3 && s[:3] == `UNC` {
			// 返回路径，如\\server\share\。。。
			return `\` + s[3:], nil
		}
		return s, nil
	}
	return "", errors.New("GetFinalPathNameByHandle returned unexpected path: " + s)
}

func readlink(path string) (string, error) {
	h, err := openSymlink(path)
	if err != nil {
		return "", err
	}
	defer syscall.CloseHandle(h)

	rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
	var bytesReturned uint32
	err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
	if err != nil {
		return "", err
	}

	rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
	switch rdb.ReparseTag {
	case syscall.IO_REPARSE_TAG_SYMLINK:
		rb := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME))
		s := rb.Path()
		if rb.Flags&windows.SYMLINK_FLAG_RELATIVE != 0 {
			return s, nil
		}
		return normaliseLinkPath(s)
	case windows.IO_REPARSE_TAG_MOUNT_POINT:
		return normaliseLinkPath((*windows.MountPointReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME)).Path())
	default:
		// 路径不是符号链接或连接，而是另一种类型的重新分析
		// 点
		return "", syscall.ENOENT
	}
}

// Readlink返回指定符号链接的目标。
// 如果有错误，则类型为*PathError。
func Readlink(name string) (string, error) {
	s, err := readlink(fixLongPath(name))
	if err != nil {
		return "", &PathError{Op: "readlink", Path: name, Err: err}
	}
	return s, nil
}
